/*
	The MIT License

	Copyright (c) 2010 IFMO/GameDev Studio

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/
// fmod_sys.cpp - 

#include "fmod_local.h"

const float MAX_WORLD_SIZE = 10000.0f*10000.0f;

/*-----------------------------------------------------------------------------
	Interface stuff :
-----------------------------------------------------------------------------*/

//
// CreateSoundSystem
//
DLL_EXPORT ISoundSystem *CreateSoundSystem ( void )
{
	return new EFMODSoundSystem();
}

/*-----------------------------------------------------------------------------
	EFMODSoundSystem
-----------------------------------------------------------------------------*/

//
// EFMODSoundSystem::EFMODSoundSystem
//
EFMODSoundSystem::EFMODSoundSystem ( void )
{	
	LOG_SPLIT("FMOD Audio system initialization");

	
	
	CONFIG_REGISTER_VAR( fmod_prof,			false );
	CONFIG_REGISTER_VAR( fmod_show_stats,	false );
	
	CONFIG_REGISTER_VAR( sound_spread,			0.0f		);
	CONFIG_REGISTER_VAR( sound_dopler_level,	1			);
	CONFIG_REGISTER_VAR( sound_max_distance,	10000.0f	);
	CONFIG_REGISTER_VAR( sound_min_distance,	1.0f		);

    FMOD_RESULT				result;
    bool					listenerflag = true;
    FMOD_VECTOR				listenerpos  = { 0.0f, 0.0f, 0.0f };
    unsigned int			version;
    FMOD_SPEAKERMODE		speakermode;
    FMOD_CAPS				caps;
    char					name[256];
    int						numdrivers;
	FMOD_ADVANCEDSETTINGS	adv_settings;

	
	//													   
	//	Init FMOD :
	//
    FMOD_CHECK( FMOD::System_Create(&fmod_sys) );
    
    FMOD_CHECK( fmod_sys->getVersion(&version) );
    
    LOGF("FMOD runtime : %02X.%02X.%02X", (version>>16)&0xFF, (version>>8)&0xFF, (version)&0xFF);
    LOGF("FMOD library : %02X.%02X.%02X", (FMOD_VERSION>>16)&0xFF, (FMOD_VERSION>>8)&0xFF, (FMOD_VERSION)&0xFF);

    if (version < FMOD_VERSION) {
        FATAL(va("Error!  You are using an old version of FMOD %08x.  This program requires %08x\n", version, FMOD_VERSION));
    }
    
    if (version!=FMOD_VERSION) {
		SIGNAL(	"Versions of FMOD runtime and linked library are diferrent.\r\n"
				"Unpredictable behavior can occur.\r\n"
				"Press \"Continue\"");
    }

	//
	//	Get drivers :
	//
    result = fmod_sys->getNumDrivers(&numdrivers);
    FMOD_CHECK(result);

    if (numdrivers == 0) {
		LOG_WARNING("No audio drivers found");
        result = fmod_sys->setOutput(FMOD_OUTPUTTYPE_NOSOUND);
        FMOD_CHECK(result);
    }
    else {
    
		for (int i=0; i<numdrivers; i++) {
			fmod_sys->getDriverInfo(i, name, 255, NULL);
			LOGF("sound driver %d : %s", i, name);
		}
    
        FMOD_CHECK( fmod_sys->getDriverCaps(0, &caps, 0, 0, &speakermode) );

		//	Set the user selected speaker mode.
        FMOD_CHECK( fmod_sys->setSpeakerMode(speakermode) );

		//	The user has the 'Acceleration' slider set to off!  
		//	This is really bad for latency!.
		//	You might want to warn the user about this.
        if (caps & FMOD_CAPS_HARDWARE_EMULATED) {                                                   
			LOG_WARNING("Audio Acceleration slider is set to off (bad latency)");
            result = fmod_sys->setDSPBufferSize(1024, 10);
            FMOD_CHECK(result);
        }

        result = fmod_sys->getDriverInfo(0, name, 256, 0);
        FMOD_CHECK(result);

		//	Sigmatel sound devices crackle for some reason if the format is PCM 16bit.  
		//	PCM floating point output seems to solve it.
        if (strstr(name, "SigmaTel")) {
            FMOD_CHECK( fmod_sys->setSoftwareFormat(48000, FMOD_SOUND_FORMAT_PCMFLOAT, 0,0, FMOD_DSP_RESAMPLER_LINEAR) );
        }
    }
    
	//
	//	Continue initialization stuff :
	//
	uint	dbg_init_flags = 0;
	uint	init_flags = FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_SOFTWARE_OCCLUSION | FMOD_INIT_NORMAL /*| FMOD_INIT_SOFTWARE_HRTF*/;
    memset(&adv_settings, 0, sizeof(adv_settings));
    adv_settings.cbsize	=	sizeof(adv_settings);
    
    if ( fmod_prof->Bool() ) {
		LOGF("FMOD remote profiling enabled" );
		dbg_init_flags |= FMOD_INIT_ENABLE_PROFILE;
    }
	FMOD_CHECK( fmod_sys->setSoftwareChannels(100) );
	// virtual channel count, can be higher than actual channels
    result = fmod_sys->init(200, init_flags | dbg_init_flags, 0);
    
    // Ok, the speaker mode selected isn't supported by this soundcard.  Switch it back to stereo...
    if (result == FMOD_ERR_OUTPUT_CREATEBUFFER) {
		LOG_WARNING("Selected speaker mode is not supported");
		LOGF("Switching back to stereo mode");
        FMOD_CHECK( fmod_sys->setSpeakerMode(FMOD_SPEAKERMODE_STEREO) );
        FMOD_CHECK( fmod_sys->init(200, FMOD_INIT_NORMAL | dbg_init_flags, 0) );/* ... and re-init. */
    };

	FMOD_CHECK( fmod_sys->setAdvancedSettings( &adv_settings ) );

	FMOD_CHECK( fmod_sys->createChannelGroup("3d sounds", &sound3d_channels) );

	LOG_SPLIT("");
}


//
// EFMODSoundSystem::~EFMODSoundSystem
//
EFMODSoundSystem::~EFMODSoundSystem ( void )
{
	LOG_SPLIT("FMOD Audio system shutting down");
			
		FreeSounds();
		
		//int max_alloced;
		//int current_alloced;
		//int total_alloced;
		//FMOD_CHECK(fmod_sys->getSoundRAM(&current_alloced, &max_alloced, &total_alloced));
		//LOG_WARNING(va("current=%d max=%d total=%d\r\n", current_alloced, max_alloced, total_alloced));
		
		FMOD_CHECK( fmod_sys->close() );
		FMOD_CHECK( fmod_sys->release() );		

	LOG_SPLIT("");
}


//
// EFMODSoundSystem::FreeSounds
//
void EFMODSoundSystem::FreeSounds ( void )
{
	sound3d_file_list.clear();
	sound2d_file_list.clear();
	sound3d_list.clear();
	sound_track_list.clear();
	sound_geometry_list.clear();
	sound_reverb_list.clear();

	sound3d_channels->release();

}


//
// EFMODSoundSystem::Play2DSound
//
void EFMODSoundSystem::Play2DSound ( const char *path, float volume, float panning )
{
	FMOD::Channel		*fmod_channel	= NULL;
	EFMODSoundBuffer	*sound_file		= RegisterSoundBuffer(path, false).getPointer();	

	if (fmod_sys && sound_file)
	{
		FMOD_CHECK( fmod_sys->playSound( FMOD_CHANNEL_FREE, sound_file->GetFMODSound(), true, &fmod_channel ) );
	}

	if (fmod_channel)
	{
		FMOD_CHECK( fmod_channel->setVolume(volume) );
		FMOD_CHECK( fmod_channel->setPan(panning) );
		FMOD_CHECK( fmod_channel->setPaused(false) );
	}
}


//
// EFMODSoundSystem::SetListener
//
void EFMODSoundSystem::SetListener ( const EPoint &pos, const EVector &view, const EVector &up, const EVector &vel )
{
	FMOD_VECTOR listener_vector	= { pos.x,	pos.y,	pos.z	};
	FMOD_VECTOR view_vector		= { view.x, view.y, view.z	};
	FMOD_VECTOR up_vector		= { up.x,	up.y,	up.z	};
	FMOD_VECTOR vel_vector		= { vel.x,	vel.y,	vel.z	};	

	FMOD_CHECK( fmod_sys->set3DListenerAttributes(0, &listener_vector, &vel_vector, &view_vector, &up_vector) );
}


//
// EFMODSoundSystem::CreateSoundSource
//
IPxSound3D EFMODSoundSystem::CreateSoundSource ( const char *path )
{
	EPxFMODSoundBuffer	sound_file		= RegisterSoundBuffer(path, true);
	IPxSound3D			sound_temp;

	if (sound_file)
	{	
		sound_temp = new EFMODSound3D(this, sound_file.getPointer());
		sound3d_list.push_back(sound_temp);
	}

	return sound_temp;
}


//
// EFMODSoundSystem::DestroySoundSource
//
void EFMODSoundSystem::DestroySoundSource ( ISound3D *sound )
{
}


//
// EFMODSoundSystem::CreateChannel
//
FMOD::Channel *EFMODSoundSystem::CreateChannel ( FMOD::Sound *sound )
{
	FMOD::Channel *fmod_channel	= NULL;

	if (sound && fmod_sys)
	{	
		FMOD_CHECK( fmod_sys->playSound( FMOD_CHANNEL_FREE, sound, true, &fmod_channel ) );

		if (fmod_channel && sound3d_channels)
		{
			fmod_channel->setChannelGroup(sound3d_channels);
		}
	}
	
	

	return fmod_channel;
}


//
// EFMODSoundSystem::RegisterSoundBuffer
//
EPxFMODSoundBuffer EFMODSoundSystem::RegisterSoundBuffer ( const char *path, bool sound3d )
{
	if(sound3d)
	{
		for (uint i = 0; i < sound3d_file_list.size(); i++)
		{
			if ( strcmp( path, sound3d_file_list[i]->Name() ) == 0 ) 
			{
				EPxFMODSoundBuffer buffer = sound3d_file_list[i];
				return buffer;
			}
		}
	}
	else
	{
		for (uint i = 0; i < sound2d_file_list.size(); i++)
		{
			if ( strcmp( path, sound2d_file_list[i]->Name() ) == 0 ) 
			{
				EPxFMODSoundBuffer buffer = sound2d_file_list[i];
				return buffer;
			}
		}
	}

	LOGF(va("Loading sound : %s", path));

	EPxFMODSoundBuffer sound_file = new EFMODSoundBuffer( fmod_sys, path, sound3d );

	if (sound3d)
	{
		sound3d_file_list.push_back(sound_file);
	}
	else
	{
		sound2d_file_list.push_back(sound_file);
	}

	return sound_file;
}


//
// EFMODSoundSystem::CreateSoundTrack
//
IPxSoundTrack	EFMODSoundSystem::CreateSoundTrack ( const char *path )
{
	FMOD::Sound			*sound			= NULL;
	IPxSoundTrack		track			= NULL;

	if (fmod_sys)
	{	
		LOGF("Loading sound track: %s", path);
		
		try 
		{
			FMOD_CHECK( fmod_sys->createStream( path, FMOD_SOFTWARE | FMOD_LOOP_NORMAL | FMOD_2D, NULL, &sound ) );
		}
		catch (exception &e) 
		{
			LOG_WARNING("%s\r\n", e.what());

			return new EFMODSoundTrack( this, NULL );
		}
					
		if (sound)
		{
			track = new EFMODSoundTrack ( this, sound );
			sound_track_list.push_back(track);
		}
		
	}

	return track;
}


//
// EFMODSoundSystem::DestroySoundTrack
//
void EFMODSoundSystem::DestroySoundTrack ( ISoundTrack *track )
{
}


//
// EFMODSoundSystem::SetMute
//
void EFMODSoundSystem::SetMute ( bool mute )
{
	sound3d_channels->setMute(mute);
	/*for ( uint i = 0; i < sound3d_list.size(); i++ )
	{
		sound3d_list[i]->SetMute(mute);
	}*/

	for ( uint i = 0; i < sound_track_list.size(); i++ )
	{
		sound_track_list[i]->SetMute(mute);
	}
}


//
// EFMODSoundSystem::Frame
//
void EFMODSoundSystem::Frame ( void )
{
	if (fmod_sys)
	{
		FMOD_CHECK(fmod_sys->update());
	}

	if (sound3d_channels)
	{
		//sound3d_channels->setMute(mute);
		//sound3d_channels->setVolume(volume);
		//channels
	}

}


//
// EFMODSoundSystem::RenderDebug
//
void EFMODSoundSystem::RenderDebug ( void )
{
	if (!fmod_show_stats->Bool()) {
		return;
	}
	
	DEBUG_STRING("---- FMOD stats ----");

	int hw_num_2d = 0,  hw_num_3d = 0,  hw_num_total = 0;
	int sw_num_total = 0;
	FMOD_CHECK(fmod_sys->getHardwareChannels(&hw_num_2d, &hw_num_3d, &hw_num_total));
	FMOD_CHECK(fmod_sys->getSoftwareChannels(&sw_num_total));

	DEBUG_STRING("HW 2d:%d 3d:%d total:%d SW num:%d",  hw_num_2d,  hw_num_3d,  hw_num_total, sw_num_total );
	

	int num_channels = 0;
	FMOD_CHECK(sound3d_channels->getNumChannels(&num_channels));

	DEBUG_STRING("Current num: %d",  num_channels );


	for ( uint i = 0; i < num_channels; i++ )
	{
		FMOD::Channel	*channel = NULL;
		FMOD::Sound		*sound = NULL;
		FMOD_CHECK(sound3d_channels->getChannel(i, &channel));
		
		FMOD_VECTOR pos, vel;
		uint sound_pos = 0, length = 0;
		float min_dist = -1.0f, max_dist = -1.0f;
		bool virt = false;

		FMOD_CHECK(channel->get3DAttributes(&pos, &vel));
		FMOD_CHECK(channel->getPosition( &sound_pos, FMOD_TIMEUNIT_MS ));
		FMOD_CHECK(channel->getCurrentSound(&sound));
		FMOD_CHECK(channel->isVirtual(&virt));
		FMOD_CHECK(channel->get3DMinMaxDistance(&min_dist, &max_dist));

		FMOD_CHECK(sound->getLength(&length, FMOD_TIMEUNIT_MS));

		DEBUG_STRING("v:%d time:%3d/%-3d pos %5.2f:%5.2f:%5.2f vel %5.2f:%5.2f:%5.2f  min dist:%5.2f  max_dist:%5.2f", virt, sound_pos, length,  pos.x, pos.y, pos.z, vel.x, vel.y, vel.z, min_dist, max_dist );
	}
	
	int num_listeners;
	fmod_sys->get3DNumListeners(&num_listeners);
	DEBUG_STRING("Listeners num: %d", num_listeners);

	FMOD_VECTOR list_pos, list_vel, list_forward, list_up;
	fmod_sys->get3DListenerAttributes(0, &list_pos, &list_vel, &list_forward, &list_up);
	DEBUG_STRING("lpos %5.2f:%5.2f:%5.2f lvel %5.2f:%5.2f:%5.2f lforw %5.2f:%5.2f:%5.2f lup %5.2f:%5.2f:%5.2f", list_pos.x, list_pos.y, list_pos.z, list_vel.x, list_vel.y, list_vel.z, list_forward.x, list_forward.y, list_forward.z, list_up.x ,list_up.y, list_up.z);

	float dopplerscale, distancefactor, rolloffscale;
	fmod_sys->get3DSettings(&dopplerscale, &distancefactor, &rolloffscale);
	DEBUG_STRING("dopplerscale: %5.2f distancefactor: %5.2f rolloffscale: %5.2f", dopplerscale, distancefactor, rolloffscale);
}

